package pl.th.permission;

import android.Manifest;
import android.annotation.SuppressLint;
import android.app.Activity;
import android.app.AppOpsManager;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.os.Build;
import android.os.Environment;
import android.provider.Settings;

import androidx.appcompat.app.AppCompatActivity;
import androidx.core.app.ActivityCompat;
import androidx.core.app.NotificationManagerCompat;
import androidx.core.content.ContextCompat;
import androidx.fragment.app.Fragment;

import java.util.HashSet;
import java.util.Set;

import static pl.th.permission.Utils.*;

@SuppressLint("InlinedApi")
public class PermissionCompat {

    public static final String PERMISSION_CREATE_NOTIFICATION = "android.permission.CREATE_NOTIFICATION";
    public static final String PERMISSION_GROUP_NOTIFICATION = "android.permission_group.CREATE_NOTIFICATION";

    private static final Set<String> SPECIAL_PERMISSIONS = new HashSet<>();

    static {
        SPECIAL_PERMISSIONS.add(PERMISSION_CREATE_NOTIFICATION);
        SPECIAL_PERMISSIONS.add(Manifest.permission.MANAGE_EXTERNAL_STORAGE);
        SPECIAL_PERMISSIONS.add(Manifest.permission.REQUEST_INSTALL_PACKAGES);
        SPECIAL_PERMISSIONS.add(Manifest.permission.SYSTEM_ALERT_WINDOW);
        SPECIAL_PERMISSIONS.add(Manifest.permission.WRITE_SETTINGS);
        SPECIAL_PERMISSIONS.add(Manifest.permission.PACKAGE_USAGE_STATS);
    }

    @SuppressWarnings("BooleanMethodIsAlwaysInverted")
    public static <T extends ProxyFragment> boolean requestPermission(int requestCode,
                                                                      String[] permissions,
                                                                      Class<T> clz, Object o) {
        AppCompatActivity appCompatActivity;
        if (o instanceof Fragment) {
            appCompatActivity = (AppCompatActivity)((Fragment)o).getActivity();
        } else {
            appCompatActivity = (AppCompatActivity)o;
        }
        boolean result = isAllPermissionsGranted(appCompatActivity, permissions);
        if (!result) {
            Callback callback = (Callback)o;
            requestPermission(requestCode, permissions, clz, callback, appCompatActivity);
        }
        return result;
    }

    public static <T extends ProxyFragment> void requestOrCallbackPermission(int requestCode,
                                                                             String[] permissions,
                                                                             Class<T> clz,
                                                                             Object o) {
        AppCompatActivity appCompatActivity;
        if (o instanceof Fragment) {
            appCompatActivity = (AppCompatActivity)((Fragment)o).getActivity();
        } else {
            appCompatActivity = (AppCompatActivity)o;
        }
        boolean result = Utils.isAllPermissionsGranted(permissions, appCompatActivity);
        Callback callback = (Callback)o;
        if (!result) {
            requestPermission(requestCode, permissions, clz, callback, appCompatActivity);
        } else {
            callback.onPermissionGranted(requestCode, permissions);
        }
    }

    private static <T extends ProxyFragment> void requestPermission(int requestCode,
                                                                    String[] permissions,
                                                                    Class<T> clz,
                                                                    Callback callback,
                                                                    AppCompatActivity activity) {
        Request request = new Request(requestCode, permissions);
        try {
            ProxyFragment fragment = clz.newInstance();
            fragment.setRequest(request);
            fragment.setCallback(callback);
            activity.getSupportFragmentManager()
                    .beginTransaction()
                    .add(fragment, fragment.getClass().getName())
                    .commitNowAllowingStateLoss();
        } catch (Exception e) {
            e.printStackTrace();
            (callback).onPermissionDenied(requestCode, permissions);
        }
    }

    public static boolean isAllPermissionsGranted(Activity activity, String[] permissions) {
        for (String permission : permissions) {
            if (SPECIAL_PERMISSIONS.contains(permission)){
                if(!isSpecialPermissionGranted(activity, permission)) {
                    return false;
                }
            } else if (!isRuntimePermissionGranted(activity, permission)) {
                return false;
            }
        }
        return true;
    }

    public static boolean isPermissionGranted(Activity activity, String permission) {
        if (isSpecialPermission(permission)){
            return isSpecialPermissionGranted(activity, permission);
        } else {
            return isRuntimePermissionGranted(activity, permission);
        }
    }

    static boolean isPermissionAlwaysDenied(Activity activity, String permission) {
        if (!isAndroidMAndUp(Build.VERSION.SDK_INT)) return false;
        if (isSpecialPermission(permission)) return false;
        return ContextCompat.checkSelfPermission(activity, permission) != PackageManager.PERMISSION_GRANTED
                && !ActivityCompat.shouldShowRequestPermissionRationale(activity, permission);
    }

    static boolean isSpecialPermission(String permission) {
        return SPECIAL_PERMISSIONS.contains(permission);
    }

    public static boolean isNotificationPerGranted(Context context) {
        return NotificationManagerCompat.from(context).areNotificationsEnabled();
    }

    @SuppressLint("NewApi")
    public static boolean isInstallPerGranted(Context context) {
        final int osVersion = Build.VERSION.SDK_INT;
        final int targetVersion = context.getApplicationInfo().targetSdkVersion;
        return !isAndroidOAndUp(osVersion) || !isAndroidOAndUp(targetVersion)
                || context.getPackageManager().canRequestPackageInstalls();
    }

    @SuppressLint("NewApi")
    public static boolean isManageExtStorageGranted(Context context) {
        final int osVersion = Build.VERSION.SDK_INT;
        final int targetVersion = context.getApplicationInfo().targetSdkVersion;
        if (isAndroidRAndUp(osVersion) && isAndroidRAndUp(targetVersion)) {
            return Environment.isExternalStorageManager();
        } else {
            return isRuntimePermissionGranted(context, Manifest.permission.WRITE_EXTERNAL_STORAGE) ||
                    isRuntimePermissionGranted(context, Manifest.permission.READ_EXTERNAL_STORAGE);
        }
    }

    @SuppressLint("NewApi")
    public static boolean isWriteSettingPerGranted(Context context) {
        final int osVersion = Build.VERSION.SDK_INT;
        final int targetVersion = context.getApplicationInfo().targetSdkVersion;
        return !isAndroidMAndUp(osVersion) || !isAndroidOAndUp(targetVersion)
                || Settings.System.canWrite(context);
    }

    @SuppressLint("NewApi")
    public static boolean isAlertWindowPerGranted(Context context) {
        final int osVersion = Build.VERSION.SDK_INT;
        final int targetVersion = context.getApplicationInfo().targetSdkVersion;
        return !isAndroidMAndUp(osVersion) || !isAndroidMAndUp(targetVersion) ||
                Settings.canDrawOverlays(context);
    }

    @SuppressLint("NewApi")
    public static boolean isPackageUsePerGranted(Context context) {
        final int osVersion = Build.VERSION.SDK_INT;
        if (isAndroidLAndUp(osVersion)) {
            AppOpsManager appOps = (AppOpsManager)
                    context.getSystemService(Context.APP_OPS_SERVICE);
            int mode;
            if (isAndroidQAndUp(osVersion)) {
                mode = appOps.unsafeCheckOpNoThrow(AppOpsManager.OPSTR_GET_USAGE_STATS,
                        context.getApplicationInfo().uid, context.getPackageName());
            } else {
                mode = appOps.checkOpNoThrow(AppOpsManager.OPSTR_GET_USAGE_STATS,
                        context.getApplicationInfo().uid, context.getPackageName());
            }
            return mode == AppOpsManager.MODE_ALLOWED;
        }
        return true;
    }

    @SuppressLint("InlinedApi")
    public static boolean isAccessBGLocationGranted(Context context) {
        final int osVersion = Build.VERSION.SDK_INT;
        boolean fineLocEnabled = isCommonRuntimePermissionGranted(context,
                Manifest.permission.ACCESS_FINE_LOCATION
        );
        boolean coarseLocEnabled = isCommonRuntimePermissionGranted(context,
                Manifest.permission.ACCESS_COARSE_LOCATION
        );
        if (!isAndroidQAndUp(osVersion)) return fineLocEnabled || coarseLocEnabled;
        if (!fineLocEnabled && !coarseLocEnabled) return false;
        return isCommonRuntimePermissionGranted(context,
                Manifest.permission.ACCESS_BACKGROUND_LOCATION);
    }

    public static boolean isRuntimePermissionGranted(Context context, String permission) {
        final int targetVersion = context.getApplicationInfo().targetSdkVersion;
        if (Manifest.permission.ACCESS_BACKGROUND_LOCATION.equals(permission)) {
            return isAccessBGLocationGranted(context);
        }
        final int osVersion = Build.VERSION.SDK_INT;
        if(isAndroidRAndUp(osVersion) && isAndroidRAndUp(targetVersion)) {
            if (Manifest.permission.READ_EXTERNAL_STORAGE.equals(permission)
                    || Manifest.permission.WRITE_EXTERNAL_STORAGE.equals(permission)) {
                return Environment.isExternalStorageManager();
            }
        }
        if(Manifest.permission.ANSWER_PHONE_CALLS.equals(permission)) {
            if (!isAndroidOAndUp(osVersion)) return true;
        }
        if (Manifest.permission.READ_PHONE_NUMBERS.equals(permission)) {
            if (!isAndroidOAndUp(osVersion)) {
                return isCommonRuntimePermissionGranted(context, Manifest.permission.READ_PHONE_STATE);
            }
        }
        if (Manifest.permission.ACTIVITY_RECOGNITION.equals(permission)) {
            if (!isAndroidQAndUp(osVersion)) {
                return true;
            }
        }
        if (Manifest.permission.ACCESS_MEDIA_LOCATION.equals(permission)) {
            if (!isAndroidQAndUp(osVersion)) {
                return true;
            }
        }
        if (Manifest.permission.BLUETOOTH_SCAN.equals(permission)) {
            if (!isAndroidSAndUp(osVersion)) {
                return isCommonRuntimePermissionGranted(context, Manifest.permission.ACCESS_COARSE_LOCATION);
            }
        }
        if (Manifest.permission.BLUETOOTH_CONNECT.equals(permission) ||
                Manifest.permission.BLUETOOTH_ADVERTISE.equals(permission)) {
            if (!isAndroidSAndUp(osVersion)) {
                return true;
            }
        }
        if (Manifest.permission.ACCEPT_HANDOVER.equals(permission)) {
            if (!isAndroidPAndUp(osVersion)) {
                return true;
            }
        }
        return isCommonRuntimePermissionGranted(context, permission);
    }

    private static boolean isCommonRuntimePermissionGranted(Context context, String permission) {
        return ContextCompat.checkSelfPermission(context, permission) == PackageManager.PERMISSION_GRANTED;
    }

    public static boolean isSpecialPermissionGranted(Context context, String permission) {
        return (Manifest.permission.WRITE_SETTINGS.equals(permission) && isWriteSettingPerGranted(context))
                || (Manifest.permission.SYSTEM_ALERT_WINDOW.equals(permission) && isAlertWindowPerGranted(context))
                || (PERMISSION_CREATE_NOTIFICATION.equals(permission) && isNotificationPerGranted(context))
                || (Manifest.permission.MANAGE_EXTERNAL_STORAGE).equals(permission) && isManageExtStorageGranted(context)
                || (Manifest.permission.PACKAGE_USAGE_STATS.equals(permission) && isPackageUsePerGranted(context))
                || (Manifest.permission.REQUEST_INSTALL_PACKAGES.equals(permission) && isInstallPerGranted(context));

    }

    public static Intent buildAppDetailIntent(Context context) {
        Intent intent = new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS);
        intent.setData(buildPackageUri(context));
        return intent;
    }

}
